package cryptoutil

import (
	"crypto/x509"
	"fmt"
	"github.com/hyperledger/fabric/bccsp"
	"github.com/hyperledger/fabric/bccsp/factory"
	"io/ioutil"
	"path/filepath"
)

func DefaultFactoryOpt() *factory.FactoryOpts {
	return &factory.FactoryOpts{
		ProviderName: "SW",
		SwOpts: &factory.SwOpts{
			SecLevel:   256,
			HashFamily: "SHA2",
			//Ephemeral:true,
		},
	}
}

type CryptoOption struct {
	HashFamily string
}

type CryptoSuite interface {
	NewSigner() (Signer, error)
	GetCreator() ([]byte, error)
}

type localCryptoSuite struct {
	cert       *x509.Certificate
	mspId      string
	factoryOpt *factory.FactoryOpts
}

func NewCryptoSuite(certDir, mspID string) (CryptoSuite, error)  {
	return newCryptoSuiteFromLocal(certDir, mspID)
}

func newCryptoSuiteFromLocal(dir, mspid string) (*localCryptoSuite, error) {
	certDir := filepath.Join(dir, "signcerts")
	keyDir := filepath.Join(dir, "keystore")
	certBytes, err := getPemMaterialFromDir(certDir)
	if err != nil || len(certBytes) == 0 {
		return nil, fmt.Errorf("get certfile fail:%v, certfile:%s", err, certDir)
	}
	cert, err := getCertFromPem(certBytes[0])
	if err != nil {
		return nil, err
	}
	cryptoSuite := &localCryptoSuite{
		cert:       cert,
		mspId:      mspid,
		factoryOpt: DefaultFactoryOpt(),
	}
	cryptoSuite.factoryOpt.SwOpts.FileKeystore = &factory.FileKeystoreOpts{KeyStorePath: keyDir}
	return cryptoSuite, nil
}

func (suite localCryptoSuite) newbccsp() (bccsp.BCCSP, error) {
	f := &factory.SWFactory{}
	swbccsp, err := f.Get(suite.factoryOpt)
	if err != nil {
		return nil, err
	}
	return swbccsp, nil
}

func (suite *localCryptoSuite) NewSigner() (Signer, error) {
	bccspInst, err := suite.newbccsp()
	if err != nil {
		return nil, err
	}
	prk, err := bccspInst.KeyImport(suite.cert, &bccsp.X509PublicKeyImportOpts{Temporary: true})
	if err != nil {
		return nil, err
	}
	return &localSigner{
		bccsp:     bccspInst,
		cert:      suite.cert,
		prik:      prk,
		cryptoOpt: suite.factoryOpt,
		mspid:     suite.mspId,
	}, nil
}

func (suite *localCryptoSuite) GetCreator() ([]byte, error) {
	signer, err := suite.NewSigner()
	if err != nil {
		return nil, err
	}
	return signer.Serialize()
}

type myCryptoSuite struct {
	key Key
	cert *x509.Certificate
	mspID string
}

func GetMyCryptoSuiteFromPath(pathDir, mspID string, keyPWD []byte) (CryptoSuite, error) {
	certDir := filepath.Join(pathDir, "signcerts")
	keyDir := filepath.Join(pathDir, "keystore")
	certBytes, err := getPemMaterialFromDir(certDir)
	if err != nil || len(certBytes) == 0 {
		return nil, fmt.Errorf("get certfile fail:%v, certfile:%s", err, certDir)
	}
	keyBytes, err := getPemMaterialFromDir(keyDir)
	if err != nil || len(keyBytes) != 1 {
		return nil, fmt.Errorf("get keybytes fail:%v or multi keyfiles exist", err)
	}
	return GetMyCryptoSuiteFromBytes(certBytes[0], keyBytes[0], keyPWD, mspID)
}

func GetMyCryptoSuiteFromFile(certFile, keyFile, mspID string,  keyPwd []byte) (CryptoSuite, error) {
	certBytes, err := ioutil.ReadFile(certFile)
	if err != nil {
		return nil, fmt.Errorf("open %s fail: %v", certFile, err)
	}
	keyBytes, err := ioutil.ReadFile(keyFile)
	if err != nil {
		return nil, fmt.Errorf("open %s fail: %v", keyFile, err)
	}
	return GetMyCryptoSuiteFromBytes(certBytes, keyBytes, keyPwd, mspID)
}

func GetMyCryptoSuiteFromBytes(certByte, keyBytes, keyPwd []byte, mspID string) (CryptoSuite, error) {
	cert, err := getCertFromPem(certByte)
	if err != nil {
		return nil, err
	}
	key, err := GetPrivateKeyFromPEM(keyBytes, keyPwd)
	if err != nil {
		return nil, err
	}
	return &myCryptoSuite{key:key, cert:cert, mspID:mspID}, nil
}

func (s *myCryptoSuite) NewSigner() (Signer, error) {
	return &myCryptoSigner{
		key: s.key,
		cert: s.cert,
		mspID:s.mspID,
	}, nil
}

func (s *myCryptoSuite) GetCreator() ([]byte, error) {
	signer, err := s.NewSigner()
	if err != nil {
		return nil, err
	}
	return signer.Serialize()
}